/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package Servidor;
import Banco.ConnectionBD;
import java.io.*;
import java.net.*;
import java.sql.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
/**
 *
 * @author Leonardo
 */
public class Servidor implements Runnable{
    
    private int porta; // porta na qual o servidor irá rodar e receber sockets do cliente
    public ArrayList<ObjectOutputStream> clientOutputStreams; // ArrayList que guarda os objetos que se comunicam com os terminais, enviando mensagens para eles
    public ArrayList<Socket> clientSockets; // ArrayList que armazena sockets do cliente
    private ServerSocket serversock=null; // socket do servidor
    private Connection con=null; // conexão com o banco
    private Statement st; // objeto que executa as requisições ao banco
    private ConnectionBD c; // classe com as configurações do banco de dados
    private JTextArea a; // recebemos a area de texto da interface gráfica onde será impresso um log de atividades
    
    public void setAreaLog(JTextArea a){
        this.a = a;
    }
    
    public class ClienteVirtual implements Runnable{ // classe interna responsável por simular o cliente dentro do servidor
                                                    // basicamente funciona como um cliente virtual
        ObjectInputStream in;   // entrada de dados, recebe os objetos que são enviados por rede do cliente para o servidor
        Socket clientSocket;    // socket do cliente
        
        public ClienteVirtual(Socket socket){
            try{        
                clientSocket = socket; // cada cliente virtual guarda seu próprio socket de conexão
                in = new ObjectInputStream(clientSocket.getInputStream()); // a entrada de dados recebe o que é enviado pelo socket do cliente
            }catch(Exception e){
                e.printStackTrace();
            }
        }
        
        public void run(){ // quando cada cliente virtual é criado esse método é chamado pela Thread que será criada
            Object o1, o2;
            try{
                // dois objetos são lidos, um recebe a requisição SQL e o outro um numero que define o tipo de pedido
                // ambas estão sendo passadas de forma abstraida pois na conexão são tratadas como objetos gerais
                while((o1 = in.readObject())!=null && (o2 = in.readObject())!=null){
                    //System.out.println("Pedido Recebido do usuário");
                    a.append("\nPedido Recebido do Usuário"); // adiciona ao log de atividades
                    // chama a função responde, passando a resposta e o socket ao qual deve ser respondido
                    responda(executa_pedido(o1,o2),this.clientSocket);
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }//fecha classe interna
    
    // função que executa a busca dos dados no banco
    public ArrayList<Produto> executaBusca(Object obj){
        ArrayList<Produto> r = new ArrayList<Produto>(); // ArrayList que guarda todos os registros da tablea do banco
        ResultSet rs=null;
                try {
                    rs = st.executeQuery(obj.toString()); // executa a query, transformando o objeto em uma string
                    while(rs.next()){ // percorre todos os registros
                        Produto p = new Produto(rs.getInt(1),rs.getString(2), rs.getString(3),rs.getString(4),rs.getDouble(5),rs.getInt(6));
                        r.add(p); // o produto é criado e adicionado ao array
                    }
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
                return r; // retorna o resultado
    }
    
    
    // executa os pedidos dos clientes, de acordo com o tipo da requisição
    public Object executa_pedido(Object obj, Object tipo) throws SQLException{
        ArrayList<Produto> r = new ArrayList<Produto>(); // ArrayList temporário de produtos
        int t = (int) tipo; // converte o tipo da requisição que foi recebido em formato de objeto em inteiro
        a.append("\nPedido em execução..."); // adiciona a atividade ao log
        switch(t){
            case 0: // SELECT, realiza uma busca
                r = executaBusca(obj);
            break;
            case 1: // DELETE
                try {
                    st.executeUpdate(obj.toString()); // executa um comando de delete
                } catch (SQLException ex) {
                    Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
                }
                r = executaBusca("SELECT * FROM produtos;"); // realiza novamente a pesquisa
            break;
            case 2:
                try {
                    st.executeUpdate(obj.toString()); // realiza a alteração de um registro
                } catch (SQLException ex) {
                    Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
                }
                r = executaBusca("SELECT * FROM produtos;"); // realiza novamente a busca
            break;
            case 3:
                try {
                    st.executeUpdate(obj.toString()); // executa uma inserção
                } catch (SQLException ex) {
                    Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
                }
                r = executaBusca("SELECT * FROM produtos;"); // realiza novamente a busca
                break;
        }
        return r;
    }
    
    public void run(){ // método que executa o servidor através de uma Thread
        clientOutputStreams = new ArrayList<ObjectOutputStream>(); // cria o objeto dos array lists que guardam as os canais de envio de mensagens
        clientSockets = new ArrayList<Socket>(); // cria também o array lists
        try{
            c = new ConnectionBD(); // cria o objeto da classe de configuração da conexão com o banco
            con = c.conecta(); // conecta ao banco
                st = con.createStatement();
                serversock = new ServerSocket(porta); // coloca o servidor no ar em uma determinada porta
                while(true){ // fica rodando a espera de conexões de clientes
                    Socket clientSocket = serversock.accept(); // quando um cliente tenta se conectar o socket do cliente é aceito pelo servidor
                    ObjectOutputStream out = new ObjectOutputStream(clientSocket.getOutputStream()); // 
                    clientOutputStreams.add(out); // adiciona a stream de saida de dados para o cliente ao array
                    clientSockets.add(clientSocket); // adiciona o socket a um array list
                    //System.out.println("Conectado");
                    a.append("\nUsuário Conectado"); // adiciona a ativeidade ao log
                    Thread t = new Thread(new ClienteVirtual(clientSocket)); // cria um cliente virtual e passa como parametro a um thread que ficará concorrendo com os demais clientes virtuais e com o servidor
                    t.start(); // inicia as atividades do cliente virtual
                }
        }catch(SQLException e){
            JOptionPane.showMessageDialog(null, "Erro no Banco de Dados!");
            e.printStackTrace();
        }catch(Exception e){
            JOptionPane.showMessageDialog(null, "Erro ao conectar a rede!");
            serversock = null;
            e.printStackTrace();
        }
    }
    
    public void responda(Object one, Socket s){ //realiza a comunicação da mensagem a todos os clientes
        // cria iterações para os arrays de objetos de envio de dados e para o array de sockets
        Iterator it = clientOutputStreams.iterator();
        Iterator itc = clientSockets.iterator();
        while(it.hasNext()&&itc.hasNext()){ // verificamos se existe o próximo elemento do array
            try{
                ObjectOutputStream out = (ObjectOutputStream) it.next();
                Socket ts = (Socket) itc.next();
                if(ts==s){ // quando encontramos o socket certo podemos devolver a resposta para o cliente
                    
                    //System.out.println(one);
                    // notamos que procuramos pelo scoket de cliente certo ao mesmo tempo que percorremos o array de ObjectOutput
                    // ou seja, quando encontrarmos o socket certo sabemos que o seu objeto de comunicação do cliente esta na mesma posição
                    // no outro array
                    out.writeObject(one); // enviamos a resposta ao cliente correto
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    
    public void setPort(int porta){
        this.porta = porta;
    }
    
    public int getPort(){
        return porta;
    }
    
    public boolean getStatusBD(){
        return con!=null;
    }
    
    public boolean getStatus(){
        return serversock!=null;
    }
    
    public Object closeConection(){
        try {
            serversock.close();
            serversock = null;
        } catch (IOException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        }
        return serversock;
    }
}